package parser

import (
	"fmt"
	"learning/goclac/tokens"
	"strconv"
	"strings"
)

type tParser struct {
	tokens []*tokens.Token
	stack []*tStackNode
	total int
	position int
}


func newParser(tokens []*tokens.Token)  *tParser {
	return &tParser{
		tokens: tokens,
		stack: make([]*tStackNode, 0),
		total : len(tokens),
		position: -1,
	}
}

func (me *tParser) showStack() string {
	lst := make([]string, 0)
	for _,it := range me.stack {
		switch it.flag {
		case NODE_VALUE:
			lst = append(lst, strconv.FormatFloat(it.value, 'f', 10, 64))
			break
		case NODE_TOKEN:
			switch it.token.Token {
			case tokens.DoubleLiteral:
				fallthrough
			case tokens.IntLiteral:
				lst = append(lst, it.token.Value)
				break

			default:
				lst = append(lst, it.token.Value)
				break
			}
		}
	}
	return strings.Join(lst, " ")
}


func (me *tParser) moreToken() bool {
	return me.position < me.total - 1
}

func (me *tParser) nextToken() *tokens.Token {
	if !me.moreToken() {
		return nil
	}

	me.position++
	return me.currentToken()
}

func (me *tParser) currentToken() *tokens.Token {
	if me.position >= me.total {
		return nil
	}
	return me.tokens[me.position]
}

func (me *tParser) reduce() {
	sCurrentStack := ""
	var fnCheckState = func() {
		sStackState := me.showStack()
		if sStackState != sCurrentStack {
			sCurrentStack = sStackState
			fmt.Printf("stack => %s\n", sStackState)
		}
	}

	for {
		fnCheckState()

		if me.reduceMulOrDiv() {
			continue
		}

		if me.reduceBrackets() {
			continue
		}

		if !me.moreToken() {
			if me.reduceAddOrSub() {
				break
			}
		}

		fnCheckState()
		//time.Sleep(1*time.Second)

		break
	}
}


func (me *tParser) stackPop() *tStackNode {
	if me.isStackEmpty() {
		return nil
	}

	var iStackSize = len(me.stack)
	var last = me.stack[iStackSize - 1]
	me.stack = me.stack[:(iStackSize-1)]
	return last
}

func (me *tParser) stackPopN(n int) []*tStackNode {
	if me.isStackEmpty() {
		return nil
	}

	var iStackSize = len(me.stack)
	if iStackSize < n {
		return nil
	}

	var lstTailItems = me.stack[(iStackSize - n):]
	me.stack = me.stack[:(iStackSize-n)]
	return lstTailItems
}

func (me *tParser) stackTakeN(n int) []*tStackNode {
	if me.isStackEmpty() {
		return nil
	}

	var iStackSize = len(me.stack)
	if iStackSize < n {
		return nil
	}

	var lstHeadItems = me.stack[:n]
	me.stack = me.stack[n+1:]
	return lstHeadItems
}


func (me *tParser) stackPush(it *tStackNode) {
	me.stack = append(me.stack, it)
}

func (me *tParser) reduceMulOrDiv() bool {
	if me.isStackEmpty() {
		return false
	}

	if me.isStackRMatch(tokens.DoubleLiteral, tokens.MUL, tokens.DoubleLiteral) {
		var lstTailNodes = me.stackPopN(3)
		v1 := lstTailNodes[0].getDoubleValue()
		v2 := lstTailNodes[2].getDoubleValue()
		var v = v1*v2
		me.stackPush(newValueNode(v))
		return true

	} else if me.isStackRMatch(tokens.DoubleLiteral, tokens.DIV, tokens.DoubleLiteral) {
		var lstTailNodes = me.stackPopN(3)
		v1 := lstTailNodes[0].getDoubleValue()
		v2 := lstTailNodes[2].getDoubleValue()
		if v2 == 0 {
			panic(fmt.Sprintf("div by zero, %v / %v", v1, v2))
		}
		var v = v1/v2
		me.stackPush(newValueNode(v))
		return true
	}

	return false
}



func (me *tParser) reduceBrackets() bool {
	if me.isStackEmpty() {
		return false
	}

	if me.isStackRMatch(tokens.RB) {
		rb := me.stackPop()
		var v float64 = 0
		for {
			if me.isStackRMatch(tokens.LB, tokens.DoubleLiteral) {
				var lstTailNodes = me.stackPopN(2)
				v += lstTailNodes[1].getDoubleValue()
				me.stackPush(newValueNode(v))
				return true

			} else if me.isStackRMatch(tokens.ADD, tokens.DoubleLiteral) {
				var lstTailNodes = me.stackPopN(2)
				v1 := lstTailNodes[1].getDoubleValue()
				v = v + v1

			} else if me.isStackRMatch(tokens.SUB, tokens.DoubleLiteral) {
				var lstTailNodes = me.stackPopN(2)
				v1 := lstTailNodes[1].getDoubleValue()
				v = v - v1

			} else {
				panic(fmt.Sprintf("LB not found at %v", rb.token.Position))
			}
		}
	}


	return false
}


func (me *tParser) reduceAddOrSub() bool {
	var v float64 = 0
	for {
		if me.isStackEmpty() {
			break
		}

		if me.isStackRMatch(tokens.ADD, tokens.DoubleLiteral) {
			var lstTailNodes = me.stackPopN(2)
			v1 := lstTailNodes[1].getDoubleValue()
			v = v + v1

		} else if me.isStackRMatch(tokens.SUB, tokens.DoubleLiteral) {
			var lstTailNodes = me.stackPopN(2)
			v1 := lstTailNodes[1].getDoubleValue()
			v = v - v1

		} else if len(me.stack)==1 && me.isStackRMatch(tokens.DoubleLiteral) {
			it := me.stackPop()
			v += it.getDoubleValue()
			me.stackPush(newValueNode(v))
			return true

		} else {
			panic("invalid expression")
		}
	}

	return false
}


func (me *tParser) isStackEmpty() bool {
	return len(me.stack) == 0
}

func (me *tParser) isStackRMatch(args...tokens.TOKENS) bool {
	var iArgsSize = len(args)
	if iArgsSize <= 0 {
		return false
	}

	var iStackSize = len(me.stack)
	if iStackSize < iArgsSize {
		return false
	}

	for i,it := range args {
		var n = iStackSize - iArgsSize + i
		var xStackNode = me.stack[n]
		if it != xStackNode.getTokenType() {
			return false
		}
	}

	return true
}

func (me *tParser) isStackLMatch(args...tokens.TOKENS) bool {
	var iArgsSize = len(args)
	if iArgsSize <= 0 {
		return false
	}

	var iStackSize = len(me.stack)
	if iStackSize < iArgsSize {
		return false
	}

	for i,it := range args {
		var xStackNode = me.stack[i]
		if it != xStackNode.getTokenType() {
			return false
		}
	}

	return true
}

func (me *tParser) parse() (error, float64) {
	for {
		t := me.nextToken()
		if t == nil {
			break
		}

		me.stackPush(newTokenNode(t))
		me.reduce()
	}

	var iStackSize = len(me.stack)
	if iStackSize == 1 && me.stack[0].getTokenType() == tokens.DoubleLiteral {
		return nil, me.stack[0].getDoubleValue()
	}

	panic(fmt.Sprintf("failed parsing expression %s", me.showStack()))
}


func Parse(tokens []*tokens.Token) (error, float64) {
	parser := newParser(tokens)
	return parser.parse()
}

